﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Drawing;
using System.Threading;
using Vortex.Drawing;
using Vortex.Debugging;
using Vortex.Input;
using Vortex.SceneFramework;
using System.Runtime.InteropServices;
using Vortex.Resources;
using Vortex.IO;
using Vortex.Audio;

namespace Vortex {

	/// <summary>
	/// Base class for game which is built using Vortex2D.NET game framework
	/// </summary>
	public abstract class Game : IDisposable {
		public static readonly string DATA_ALIAS = @"data";
		public static readonly string GRAPHICS_ALIAS = @"graphics";
		public static readonly string FONTS_ALIAS = @"fonts";
		public static readonly string SOUND_ALIAS = @"sound";
		public static readonly string SHADERS_ALIAS = @"shaders";
		public static readonly string SCENES_ALIAS = @"scenes";
		public static readonly string RESOURCES_ALIAS = @"resources";
		public static readonly string CSS_ALIAS = @"css";
		public static readonly string COMMON_ALIAS = @"common";

		private static GameWindow _window;
		private static SingleContextDevice _drawingDevice;
		private static SoundDevice _soundDevice;
		private static KeyboardListener _kbListener;
		private static MouseListener _mouseListener;
		private static Canvas2D _canvas;
		private static GameTime _time = new GameTime();
		private static TimeLine _timeLine = new TimeLine();
		private static ScreenManager _graphScreen;

		private static GameCursor _gameCursor = GameCursor.Default;
		private static bool _preferSpriteCursor;
		private static GameDebugging _gameDebugging = new GameDebugging();
		

		private static bool _terminated;
		private static bool _active;
		//private bool _paused;

		#region Internal Properties

		/// <summary>
		/// Gets or sets the current instance of the game.
		/// </summary>
		/// <value>The game instance.</value>
		static Game Instance {
			get;
			set;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Gets the game sound device interface.
		/// </summary>
		/// <value>The sound device interface.</value>
		public static SoundDevice Sound {
			get { return _soundDevice; }
		}

		/// <summary>
		/// Gets the game time.
		/// </summary>
		/// <value>The game time.</value>
		public static GameTime Time {
			get { return _time; }
		}

		/// <summary>
		/// Gets the primary buffer canvas.
		/// </summary>
		/// <value>The canvas.</value>
		public static Canvas2D Canvas {
			get { return _canvas; }
		}

		/// <summary>
		/// Gets the game keyboard interface.
		/// </summary>
		/// <value>The keyboard interface.</value>
		public static IKeyboard Keyboard {
			get { return _kbListener; }
		}

		/// <summary>
		/// Gets the game mouse interface.
		/// </summary>
		/// <value>The mouse interface.</value>
		public static IMouse Mouse {
			get { return _mouseListener; }
		}

		/// <summary>
		/// Gets the game window instance.
		/// </summary>
		/// <value>The game window.</value>
		public static GameWindow Window {
			get { return _window; }
		}

		/// <summary>
		/// Gets the game domain graph screen.
		/// </summary>
		/// <value>The domain graph screen.</value>
		public static ScreenManager Screen {
			get { return _graphScreen; }
		}

		/// <summary>
		/// Gets the global game time line.
		/// </summary>
		/// <value>The time line instance.</value>
		public static TimeLine TimeLine {
			get { return _timeLine; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether change display mode on alt enter is enabled.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if [hange display mode on alt enter is enabled; otherwise, <c>false</c>.
		/// </value>
		public static bool ChangeDisplayModeOnAltEnter {
			get; set;
		}

		/// <summary>
		/// Gets the game debugging interface.
		/// </summary>
		/// <value>The debugging interface.</value>
		public static GameDebugging Debugging {
			get { return _gameDebugging; }
		}

		/// <summary>
		/// Gets or sets the game cursor. Default value <see cref="GameCursor.Default"/>.
		/// </summary>
		/// <value>The cursor used by game.</value>
		public static GameCursor Cursor {
			get { return _gameCursor; }
			set {
				_gameCursor = value;

				//we setup system cursor also...
				if (_gameCursor.SystemCursor != null) {
					_window.Form.Cursor = _gameCursor.SystemCursor;
				} else {
					_window.Form.Cursor = Cursors.Default;
				}

				UpdateCursor();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether sprite cursor is preferred.
		/// </summary>
		/// <value><c>true</c> if sprite cursor is preferred; otherwise, <c>false</c>.</value>
		public static bool PreferSpriteCursor {
			get { return _preferSpriteCursor; }
			set { _preferSpriteCursor = value; UpdateCursor(); }
		}

		#endregion

		#region Form Handlers

		private void GameWindow_Close(object sender, FormClosedEventArgs args) {
			_terminated = true;
		}

		private void GameWindow_Closing(object sender, FormClosingEventArgs args) {
			if (args.CloseReason == CloseReason.UserClosing) {
				bool cancel = false;
				if (GameClosing != null) {
					GameClosing(ref cancel);
				}
				args.Cancel = cancel;
			}
		}

		private void GameWindow_Activated(object sender, EventArgs args) {
			_active = true;
		}

		private void GameWindow_Deactivated(object sender, EventArgs args) {
			_active = false;
		}

		/// <summary>
		/// Updates the window cursor.
		/// </summary>
		private static void UpdateCursor() {
			if (null == _gameCursor || null != Cursor.CursorSprite && PreferSpriteCursor) {
				_window.Form.Cursor = GameCursor.Blank.SystemCursor;
			} else {
				_window.Form.Cursor = _gameCursor.SystemCursor;
			}
		}

		#endregion

		/// <summary>
		/// Initializes the game window.
		/// </summary>
		private void InitializeWindow() {
			_window = new GameWindow();
			_window.Title = "Vortex2D.NET Game Window";
			_window.Size = new Size(640, 480);
			_window.LocateAtDesktopCenter();
			_window.Form.FormClosing += new FormClosingEventHandler(GameWindow_Closing);
			_window.Form.FormClosed += new FormClosedEventHandler(GameWindow_Close);
			_window.Form.Activated += new EventHandler(GameWindow_Activated);
			_window.Form.Deactivate += new EventHandler(GameWindow_Deactivated);
			//UpdateCursor();
			_window.OnReset();
		}

		void InitializeInput() {
			_kbListener = new KeyboardListener();
			_mouseListener = new MouseListener(_window.Handle);

			Application.AddMessageFilter(_mouseListener);
			Application.AddMessageFilter(_kbListener);
		}

		private void InitializeGraphics() {
			_drawingDevice = new SingleContextDevice(_window.Handle);
			_canvas = _drawingDevice.Context.Canvas;
		}

		/// <summary>
		/// Initializes the sound subsystem.
		/// </summary>
		private void InitializeSound() {
			_soundDevice = new SoundDevice();
		}

		/// <summary>
		/// Initializes the domain graph subsystem.
		/// </summary>
		private void InitializeSceneGraph() {
			_graphScreen = new ScreenManager(_drawingDevice.Context, Mouse, Keyboard);
		}

		/// <summary>
		/// Does the frame processing
		/// </summary>
		private void DoFrameYield() {
			//System.Windows.Forms.Cursor.Hide();

			//check engine built-in input
			CheckSystemInput();

			//check do we need to reset window
			if (_window.IsResetRequired) {
				ResetGraphics();
				
				if (!_window.Fullscreen) {
					//locate window in the desktop center after switch back to screen mode
					_window.LocateAtDesktopCenter();
					_window.Update();
					_window.LocateAtDesktopCenter();
				}
				//if everything is fine, lets 
				_window.OnReset();
			}

			//try to start new frame and update it
			if (_drawingDevice.BeginScene()) {
				//update all of game frame time objects
				_time.UpdateFrame();

				//update keyboard listener
				_kbListener.Update();

				//update game timeline
				_timeLine.Update(_time.FrameTime);

				//call update for game code
				Update(_time);

				//set canvas default
				_canvas.SetDefaults();

				//call game debugging code
				Render(_canvas);
				
				//flush drawing device command queue
				_drawingDevice.Flush();

				//draw different debug information
				RenderDebugInformation(_canvas);

				//draw mouse cursor if required
				DrawMouseCursor();

				//finalize domain
				_drawingDevice.EndScene();

				//present domain after everything was complete
				_drawingDevice.Context.Present();
			} else {
				//ask for game reset
				_window.IsResetRequired = true;
			}
		}

		/// <summary>
		/// Draws the mouse cursor if required.
		/// </summary>
		private void DrawMouseCursor() {
			//draw cursor only if it is over window
			if (_mouseListener.IsOverWindow) {
				if (PreferSpriteCursor && _gameCursor != null && _gameCursor.CursorSprite != null) {
					_canvas.ResetTransform();
					_canvas.SetDefaults();
					DrawCursor(_canvas, _mouseListener.Location, _gameCursor.CursorSprite);
				}
			}
		}

		/// <summary>
		/// Draw cursor method. You can override it for providing custom cursor rendering with special effects or so.
		/// </summary>
		/// <param name="canvas">The target canvas.</param>
		/// <param name="location">The location of cursor.</param>
		/// <param name="spriteSource">The sprite source of cursor.</param>
		protected virtual void DrawCursor(Canvas2D canvas, Vector2 location, ISpriteSource spriteSource) {			
			canvas.DrawSprite(location, spriteSource.ToSprite(), ColorU.Blank);
		}

		/// <summary>
		/// Resets the graphics to fit new game window parameters.
		/// </summary>
		private void ResetGraphics() {
			_drawingDevice.Context.Reconfigure(_window.Size.Width, _window.Size.Height, _window.Fullscreen, _window.VerticalSync);
		}

		/// <summary>
		/// Checks the system input combinations for tracking windows more change or so.
		/// </summary>
		private void CheckSystemInput() {
			if (Game.ChangeDisplayModeOnAltEnter) {
				if (_kbListener.IsPressedCombination(Key.LeftAlt, Key.Return) || _kbListener.IsPressedCombination(Key.RightAlt, Key.Return)) {
					_window.Fullscreen = !_window.Fullscreen;
				}
			}

			if (_gameDebugging.EnableKeyboardControl) {
				//F11 toggles FPS
				if (_kbListener.IsPressed(Key.F11)) _gameDebugging.ShowFPS = !_gameDebugging.ShowFPS;
				//F12 toggles statistics
				if (_kbListener.IsPressed(Key.F12)) _gameDebugging.ShowStatistics = !_gameDebugging.ShowStatistics;
				//Grave accent toggles console
				if (_kbListener.IsPressed(Key.Grave)) _gameDebugging.ShowConsole = !_gameDebugging.ShowConsole;
			}
		}

		/// <summary>
		/// Renders the debug information.
		/// </summary>
		/// <param name="canvas">The target canvas.</param>
		private void RenderDebugInformation(Canvas2D canvas) {
			canvas.ResetTransform();

			string fpsString = GetFPSString();		
			if (_gameDebugging.ShowStatistics) {
				_drawingDevice.PerfMonitor.Draw(canvas, new Vector2(6, 6), fpsString); 
			} else if (_gameDebugging.ShowFPS) {
				canvas.DrawText(SpriteFont.Console, new Vector2(6, 6), fpsString, ColorU.White);
			}

			if (_gameDebugging.ShowConsole) {
				DrawConsole(canvas);
			}
		}

		///<summary>Left console padding</summary>
		const float CONSOLE_LEFT_PADDING = 200f;
		///<summary>Boder size of console</summary>
		const float CONSOLE_BORDER_SIZE = 4f;
		///<summary>Size of level padding in pixels</summary>
		const float CONSOLE_LEVEL_PADDING = 16f;

		/// <summary>
		/// Draws the console content.
		/// </summary>
		/// <param name="canvas">The target canvas.</param>
		private void DrawConsole(Canvas2D canvas) {
			
			SpriteFont consoleFont = SpriteFont.SmallConsole;
			if (Log.History.LineCount > 0) {
				int maxConsoleHeight = (int)MathHelper.Floor(canvas.Height / consoleFont.LineHeight);
				int visibleLineCount = Math.Min(Log.History.LineCount, maxConsoleHeight);

				//draw semitransparent background
				canvas.DrawFilledRect(
					CONSOLE_LEFT_PADDING, 0,
					canvas.Width - CONSOLE_LEFT_PADDING, maxConsoleHeight * consoleFont.LineHeight + CONSOLE_BORDER_SIZE,
					ColorU.Black.MultiplyAlpha(0.8f)
				);

				//draw all of lines
				Vector2 lineCursor = new Vector2(CONSOLE_LEFT_PADDING + 4, 0);
				Vector2 paddingOffset = Vector2.UnitX * CONSOLE_LEVEL_PADDING;
				for (int n = Log.History.LineCount - visibleLineCount; n < Log.History.LineCount; ++n) {
					LogHistoryLine line = Log.History[n];
					canvas.DrawText(consoleFont, lineCursor + paddingOffset * line.Padding, line.Value, GetColorOfSeverity(line.Severity));
					lineCursor.Y += consoleFont.LineHeight;
				}
			}
		}

		/// <summary>
		/// Gets the color of severity message.
		/// </summary>
		/// <param name="severity">The severity of message.</param>
		/// <returns>Color of message for specified severity</returns>
		private ColorU GetColorOfSeverity(Severity severity) {
			switch (severity) {
				case Severity.StackTrace: return ColorU.IndianRed.MultiplyAlpha(0.75f);
				case Severity.Success: return ColorU.Green;
				case Severity.Details: return ColorU.Grey.MultiplyAlpha(0.5f);
				case Severity.Important: return ColorU.Cyan;
				case Severity.Warning: return ColorU.Orange;
				case Severity.Error: return ColorU.Red;
				case Severity.Panic: return ColorU.Red;
				default:
					return ColorU.White;
			}
		}

		/// <summary>
		/// Returns the correcly formatted FPS string.
		/// </summary>
		/// <returns>Correcly formatted FPS string</returns>
		private static string GetFPSString() {
			float fps = _time.Fps;
			string format;
			if (fps > 100) {
				format = "FPS: {0:#}";
			} else {
				if (fps > 20) {
					format = "FPS: {0:0.#}";
				} else {
					format = "FPS: {0:0.##}";
				}
			}
			string fpsString = string.Format(format, fps);
			return fpsString;
		}

		#region Public Methods

		static Game() {
			//enable tracking unhandled exceptions
			Watcher.TrackUnhandledExceptions();

			//register global path aliases
			SetDefaultPathAliases();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Game"/> class.
		/// </summary>
		public Game() {
			//check is other isntance of game is loaded
			if (null != Instance) {
				throw new Exception("Another game instance exists. Only one game instance can exists at the same time.");
			}

			Instance = this;
			InitializeWindow();
			InitializeGraphics();			
			InitializeInput();
			InitializeSound();
			InitializeSceneGraph();

			UpdateCursor();

			//set default game options
			ChangeDisplayModeOnAltEnter = true;
		}

		/// <summary>
		/// Sets the default path aliases for Vortex Game engine.
		/// </summary>
		private static void SetDefaultPathAliases() {
			FileSystem.SetPathAlias(DATA_ALIAS, @".\data");
			FileSystem.SetPathAlias(GRAPHICS_ALIAS, @".\data\graphics");
			FileSystem.SetPathAlias(FONTS_ALIAS, @".\data\fonts");
			FileSystem.SetPathAlias(SOUND_ALIAS, @".\data\sound");
			FileSystem.SetPathAlias(SHADERS_ALIAS, @".\data\shaders");
			FileSystem.SetPathAlias(SCENES_ALIAS, @".\data\scenes");
			FileSystem.SetPathAlias(RESOURCES_ALIAS, @".\data\resources");
			FileSystem.SetPathAlias(CSS_ALIAS, @".\data\css");
			FileSystem.SetPathAlias(COMMON_ALIAS, @".\data");
		}

		/// <summary>
		/// Invoked on game resource loading before start
		/// </summary>
		protected virtual void Load() {
		}

		/// <summary>
		/// Invoked on game resource loading after Load() but in parallel thread. 
		/// It can be usefull for loading big data block on startup in background.
		/// </summary>
		protected virtual void AsyncLoad() {

		}

		/// <summary>
		/// Invoked on game resource unloading before exit
		/// </summary>
		protected virtual void Unload() {
		}

		protected virtual void Update(GameTime time) {
			;
		}

		protected virtual void Render(Canvas2D canvas) {
			canvas.Clear(ColorU.Black);
		}

		/// <summary>
		/// Terminates the game.
		/// </summary>
		public static void Terminate() {
			_terminated = true;
		}

		/// <summary>
		/// Runs instance of game
		/// </summary>
		public void Run() {
			//need to update window on startup
			_window.Update();

			//LOADING SECTION
			using (Log.ImportantUp("LOAD GAME")) {
				Load();
				//execute async loading callback
				Log.Important("Start asynchronous loading");
				CallbackHandler asyncLoadMethod = new CallbackHandler(AsyncLoad);
				asyncLoadMethod.BeginInvoke(delegate(IAsyncResult result) {
					//log that async loading complete
					Log.Important("Asynchronous loading complete");
				}, null);				
			}

			//MAIN LOOP SECTION
			using (Log.ImportantUp("MAIN LOOP")) {
				//looping until game will be terminated
				while (!_terminated) {

					//doing message events
					Application.DoEvents();

					//if window is active -> lets render new frame, otherwise - wait a little bit
					if (_active) {
						//resume sound if it is paused
						if (Sound.IsPaused) {
							Sound.IsPaused = false;
						}

						DoFrameYield();
					} else {
						//pause sound if it is still playing
						if (!Sound.IsPaused) {
							Sound.IsPaused = true;
						}

						Thread.Sleep(1);
					}
				}
			}
			//UNLOAD SECTION
			using (Log.ImportantUp("UNLOAD GAME")) {
				Unload();
			}
		}

		
		///<summary>Game closing event is raised on user pressed close window button</summary>
		public static event GameClosingHandler GameClosing;

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Performs game-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose() {
			Application.RemoveMessageFilter(_mouseListener);
			Application.RemoveMessageFilter(_kbListener);

			/*
			if (null != _soundEngine) {
				_soundEngine.Dispose();
			}
			 * */

			_kbListener.Dispose();
			_window.Destroy();
			Instance = null;
		}

		#endregion
	}

}
